//----------------------------------------------------------
// 日志落地模块：确定日志的输出方式
// 标准输出，指定文件夹写入，滚动文件（按照时间或大小进行滚动切换），支持扩展（数据库）
//----------------------------------------------------------

#ifndef __LOG_SINK__
#define __LOG_SINK__

#include "util.hpp"
#include <fstream>
#include <iostream>
#include <memory>
#include <string>
#include <unistd.h>

namespace ymbxlog
{
#define FILE_ONE_SINK_SIZE 512
    enum TimeSep{
        Second=1,
        Minute=60,
        Hour=60*60,
        Day=60*60*24,
        Week=60*60*24*7,
        HalfMonth=60*60*24*15,
        Month=60*60*24*30
    };
    class LogSink
    {
    public:
        using ptr = std::shared_ptr<LogSink>;
        virtual ~LogSink() {}
        // 日志打印输出
        virtual void Log(const std::string &str) = 0;
    };
    // 标准输出落地
    class StdoutSink : public LogSink
    {
    public:
        virtual void Log(const std::string &str)
        {
            std::cout << str;
        }
    };
    // 指定文件落地
    class FileSink : public LogSink
    {
    public:
        FileSink(const std::string &pathname) : _pathname(pathname)
        {
            if (!Util::File::IsExist(_pathname))
            {
                Util::File::CreatDir(Util::File::GetDir(_pathname));
            }
        }
        virtual void Log(const std::string &str)
        {
            _ofs.open(_pathname, std::ios::binary | std::ios::app);
            if (!_ofs.is_open())
            {
                std::cerr << "打开文件失败\n";
                abort();
            }
            _ofs << str;
            if (!_ofs.good())
            {
                std::cout << "写入文件失败\n";
            }
            _ofs.close();
        }

    private:
        std::string _pathname;
        std::ofstream _ofs;
    };
    
    // ⽇志⽂件滚动的条件有两个:⽂件⼤⼩ 和 时间。

    // 基于文件大小的滚动文件落地
    class RollByFilesizeSink : public LogSink
    {
    public:
        RollByFilesizeSink(const std::string &basename, size_t file_max_size = FILE_ONE_SINK_SIZE) : _basename(basename), _file_max_size(file_max_size)
        {
            if (!Util::File::IsExist(_basename))
            {
                Util::File::CreatDir(Util::File::GetDir(_basename));
            }
        }
        virtual void Log(const std::string &str)
        {
            InitLog();
            _ofs.write(str.c_str(), str.size());
            _file_cur_size += str.size();
            _ofs.flush();
        }

    private:
        void InitLog()
        {
            if(!_ofs.is_open() || _file_cur_size>=_file_max_size){
                _ofs.close();
                std::string pathname = _basename + "_" + std::to_string(_file_num++);
                _ofs.open(pathname, std::ios::binary | std::ios::app);
                if (!_ofs.is_open())
                {
                    std::cout << "基于文件大小的滚动文件写入打开文件失败" << std::endl;
                    abort();
                }
                _file_cur_size = 0;
            }
        }

    private:
        std::string _basename;
        size_t _file_max_size;
        size_t _file_cur_size=0;
        size_t _file_num=0;
        std::ofstream _ofs;
    };

    // 基于时间的滚动文件落地
    class RollByTimeSink : public LogSink{
    public:
        RollByTimeSink(const std::string& basename,TimeSep time_gap=TimeSep::Minute):_basename(basename),_time_gap(time_gap){
            if(!Util::File::IsExist(_basename)){
                Util::File::CreatDir(Util::File::GetDir(_basename));
            }
        }
        virtual void Log(const std::string& str){
            InitLog();
            _ofs.write(str.c_str(),str.size());
            if(!_ofs.good()){
                std::cout<<"基于时间的滚动文件写入文件失败"<<std::endl;
                abort();
            }
            _ofs.flush();
        }
    private:
        void InitLog(){
            time_t cur_tim=Util::Date::Now();
            if(!_ofs.is_open() || _pre_tim+_time_gap<cur_tim){
                _ofs.close();
                std::string pathname=_basename+"_"+std::to_string(cur_tim);
                _ofs.open(pathname,std::ios::binary | std::ios::app);
                if(!_ofs.is_open()){
                    std::cout<<"基于时间的滚动文件写入打开文件失败"<<std::endl;
                    abort();
                }
                _pre_tim=cur_tim;
            }
        }
    private:
        std::string _basename;
        std::ofstream _ofs;
        TimeSep _time_gap;
        time_t _pre_tim;
    };

    // 通过类模板来实现对日志落地模块的扩展
    template <typename SinkType, typename... Args>
    class SinkFactory
    {
    public:
        static LogSink::ptr create(Args &&...args)
        {
            return std::make_shared<SinkType>(std::forward<Args>(args)...);
        }
    };
};

#endif